/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "MPQArchive.h"

#include <algorithm>
#include "Logger.h"

namespace PQGameCore
{
	MPQListFile::MPQListFile(MPQFile* file)
		: _file(file),_nodeList()
	{
		_root=new MPQTreeNode();
		_nodeList.insert(NodeList::value_type(_root->getName(),_root));
		char *p = (char*)file->getBuffer();
		char *end = p + file->getSize();

		while (p <= end) {
			char *q=p;
			do {
				if (*q==13){ 
					break;
				}
			} while (q++<=end);
			std::string line((char*)p,(int)(q-p));
			p = q + 2; // delete \r\n
			addLine(line);
		}
	}

	void MPQListFile::addLine(std::string line)
	{
		int pos=line.find_last_of(".");
		std::string ext=line.substr(pos+1);
		if(ext=="m2"||ext=="M2"){
			addNode(line);
		}
	}

	MPQTreeNode* MPQListFile::getTreeNode(std::string path)
	{
		NodeList::iterator it=_nodeList.find(path);
		if(it!=_nodeList.end()){
			return it->second;
		}
		return 0;
	}

	MPQTreeNode* MPQListFile::addNode(std::string path)
	{
		std::transform(path.begin(), path.end(),path.begin(), ::tolower);
		NodeList::iterator it=_nodeList.find(path);
		if(it!=_nodeList.end()){
			return it->second;
		}else{
			std::string name=getPathLastNode(path);
			MPQTreeNode* node=new MPQTreeNode(name);
			_nodeList.insert(NodeList::value_type(path,node));

			if(isLeafPath(path)){
				_root->addChild(node);
			}else{
				std::string parentPath=getParentPath(path);
				MPQTreeNode* parentNode=addNode(parentPath);
				parentNode->addChild(node);
			}
			return node;
		}
	}

	MPQArchive::ArchiveList MPQArchive::_list;
	MPQArchive::IDList MPQArchive::_idList;

	MPQArchive::MPQArchive(std::string filename,HANDLE handle)
		: _filename(filename),_handle(handle)
	{
		_id=_list.size();
		_idList.insert(IDList::value_type(_id,_filename));
		GNgLog.out("Opening archive %s\n", filename);
		initListFile();
	}
	
	/*Init list file is actually construct mpq file system tree.
	Note this is Time Waste Operation.(Any good idear?)
	*/
	void MPQArchive::initListFile()
	{
		MPQFile* file=openFile("(listfile)");
		_listFile=new MPQListFile(file);
	}

	MPQArchive::~MPQArchive()
	{
	}
	
	MPQArchive* MPQArchive::openArchive(std::string filename)
	{
		MPQArchive* archive=getArchive(filename);
		if(archive){
			return archive;//already open
		}

		HANDLE handle=0;
		bool result=SFileOpenArchive(filename.c_str(),0,0,&handle);
		if(!result){
			return 0;
		}
		archive=new MPQArchive(filename,handle);
		_list.insert(ArchiveList::value_type(filename,archive));
		return archive;
	}

	MPQArchive* MPQArchive::getArchive(std::string filename)
	{
		ArchiveList::iterator it=_list.find(filename);
		if(it!=_list.end()){
			return it->second;
		}
		return 0;
	}

	MPQTreeNode* MPQArchive::getTreeNode(std::string path)
	{
		return _listFile->getTreeNode(path);
	}

	void MPQArchive::close()
	{
		SFileCloseArchive(_handle);
	}

	//open mpq file in current mpq archive
	MPQFile* MPQArchive::openFile(const char* filename)
	{
		HANDLE fh=0;
		if( !SFileOpenFileEx(_handle, filename, 0, &fh ) ){
			return 0;
		}

		DWORD size = SFileGetFileSize(fh);
		// HACK: in patch.mpq some files don't want to open and give 1 for filesize
		if (size<=1) {
			return 0;
		}

		unsigned char* buffer= new unsigned char[size];
		SFileReadFile(fh, buffer, size);
		SFileCloseFile(fh);
		MPQFile* mpqFile=new MPQFile(filename,buffer,size);
		return mpqFile;
	}

	//open mpq file in specific mpq archive
	MPQFile* MPQArchive::openFile(MPQArchive* archive, const char* filename)
	{
		if(archive==0){
			return 0;
		}

		HANDLE fh=0;
		if( !SFileOpenFileEx(archive->getHandle(), filename, 0, &fh ) ){
			return 0;
		}

		DWORD size = SFileGetFileSize(fh);
		// HACK: in patch.mpq some files don't want to open and give 1 for filesize
		if (size<=1) {
			return 0;
		}

		unsigned char* buffer= new unsigned char[size];
		SFileReadFile(fh, buffer, size);
		SFileCloseFile(fh);
		MPQFile* mpqFile=new MPQFile(filename,buffer,size);
		return mpqFile;
	}

	bool MPQArchive::fileExist(const char* filename)
	{
		if(SFileHasFile(_handle, filename)){
			return true;
		}
		return false;
	}

	int MPQArchive::getFileSize(const char* filename)
	{
		HANDLE fh=0;
		if(SFileOpenFileEx(_handle, filename, 0, &fh)){
			DWORD filesize = SFileGetFileSize( fh );
			SFileCloseFile( fh );
			return filesize;
		}
		return 0;
	}

	int MPQArchive::extractFile(char* archivedFile, char* fileName)
	{
		HANDLE hFile  = NULL;          // Archived file handle
		HANDLE handle = NULL;          // Disk file handle
		int nError = ERROR_SUCCESS; // Result value
		if(!SFileOpenFileEx(_handle, archivedFile, 0, &hFile)){
			nError = GetLastError();
		}

		// Create the target file
		if(nError == ERROR_SUCCESS)
		{
			handle = CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);//winapi
			if(handle == INVALID_HANDLE_VALUE)
				nError = GetLastError();
		}

		// Read the file from the archive
		if(nError == ERROR_SUCCESS)
		{
			
			char  szBuffer[0x10000];
			DWORD dwBytes = 1;

			while(dwBytes > 0)
			{
				SFileReadFile(hFile, szBuffer, sizeof(szBuffer), &dwBytes, NULL);
				if(dwBytes > 0)
					WriteFile(handle, szBuffer, dwBytes, &dwBytes, NULL);
			}
		}        

		// Cleanup and exit
		if(handle != NULL)
			CloseHandle(handle);
		if(hFile != NULL)
			SFileCloseFile(hFile);
		

		return nError;
	}

	int MPQArchive::extractFile(char* archiveName, char* archivedFile, char* fileName)
	{
		HANDLE hMpq   = NULL;          // Open archive handle
		HANDLE hFile  = NULL;          // Archived file handle
		HANDLE handle = NULL;          // Disk file handle
		int nError = ERROR_SUCCESS; // Result value

		// Open an archive, e.g. "d2music.mpq"
		if(nError == ERROR_SUCCESS)
		{
			if(!SFileOpenArchive(archiveName, 0, 0, &hMpq))
				nError = GetLastError();
		}

		// Open a file in the archive, e.g. "data\global\music\Act1\tristram.wav"
		if(nError == ERROR_SUCCESS)            
		{
			if(!SFileOpenFileEx(hMpq, archivedFile, 0, &hFile))
				nError = GetLastError();
		}

		// Create the target file
		if(nError == ERROR_SUCCESS)
		{
			handle = CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);//winapi
			if(handle == INVALID_HANDLE_VALUE)
				nError = GetLastError();
		}

		// Read the file from the archive
		if(nError == ERROR_SUCCESS)
		{
			
			char  szBuffer[0x10000];
			DWORD dwBytes = 1;

			while(dwBytes > 0)
			{
				SFileReadFile(hFile, szBuffer, sizeof(szBuffer), &dwBytes, NULL);
				if(dwBytes > 0)
					WriteFile(handle, szBuffer, dwBytes, &dwBytes, NULL);
			}
		}        

		// Cleanup and exit
		if(handle != NULL)
			CloseHandle(handle);
		if(hFile != NULL)
			SFileCloseFile(hFile);
		if(hMpq != NULL)
			SFileCloseArchive(hMpq);

		return nError;
	}


	MPQFile::MPQFile(const char* filename,void *buffer,int size)
		: PQEngine::FileBase(filename), _eof(false),_buffer(buffer),_size(size),_pointer(0)
	{
		//buffer must contain data... and this class just store data ptr.
		_filename=new char[strlen(filename)];
		strcpy(_filename,filename);
	}

	MPQFile::~MPQFile()
	{
		if (_buffer) delete[] _buffer;
		_buffer = 0;
		_eof = true;
	}

	size_t MPQFile::read(void* dest, size_t bytes)
	{
		if (_eof) {
			return 0;
		}

		int rpos = _pointer + bytes;
		if (rpos > _size) {
			bytes = _size - _pointer;
			_eof = true;
		}

		memcpy(dest, (char*)_buffer+_pointer, bytes);
		_pointer = rpos;
		return bytes;
	}

	void MPQFile::seek(int offset)
	{
		_pointer = offset;
		_eof = (_pointer >= _size);
	}

	void MPQFile::seekRelative(int offset)
	{
		_pointer += offset;
		_eof = (_pointer >= _size);
	}

	int MPQFile::getSize()
	{
		return _size;
	}

	int MPQFile::getPos()
	{
		return _pointer;
	}

	void* MPQFile::getBuffer()
	{
		return _buffer;
	}

}

